from threading import Thread,Event
import sqlite3,os,socket
import librepeator
ev = Event()
ev.set()

from hashlib import sha1
from pyxmpp2.streamevents import DisconnectedEvent
from pyxmpp2.message import Message 
from pyxmpp2.jid import JID
import logging
from pyxmpp2.interfaces import EventHandler, event_handler, QUIT
from pyxmpp2.streamevents import AuthorizedEvent
from pyxmpp2.client import Client
from pyxmpp2.interfaces import presence_stanza_handler, message_stanza_handler,XMPPFeatureHandler
from random import random
from hashlib import md5
import json
from pyxmpp2.settings import XMPPSettings
import time
from xml.etree.ElementTree import tostring
from socket import gethostbyname

#配置文件 net_conf.py
import net_conf


username = ''
def get_users():
    ''' return [(jid-0,password-1,srv_jid-2,domain-3,port-4),...] '''
    global username
    ram = 'i %s'%random()
    name = 'id%s'%md5(ram.encode('utf8')).hexdigest()
    username = name
    db1 = sqlite3.connect( os.path.join(os.getcwd(), 'storge.db') )
    cu1 = db1.cursor()
    sql1 = "select domain,port from servers"
    cu1.execute(sql1)
    users = []
    for item in cu1:
        jid = '%s@%s'%(name,item[0])
        password = 'share%s'%md5( jid.encode('utf8') ).hexdigest()
        srv = 'srv@'+item[0]
        users.append( (jid, password, srv,item[0], item[1] ) )
    cu1.close()
    db1.close()
    return users

commands = {'reg':{'cmd':'reg'},
            'quit':{'cmd':'quit'},
            'want':{'cmd':'want','key':'','ident':''},
            'have':{'cmd':'have','key':[],'ident':''}, 
            'select':{'cmd':'select','key':'','ident':''},
            'wait':{'cmd':'wait','ident':''},
            'ready':{'cmd':'ready','key':'','ident':''} }
ident = {}  # ident['from_jid'] = 'IDENT'
status = {}  # jid 是否已经 reg , status[jid] == True or False
send_file = {}
#发送方：send_file[ident] = (sharename,from_jid)
        
class EvtHandler(EventHandler):
    user = None
    win = None
    def __init__(self, user,win):
        global status
        self.user = user
        self.win = win
        status[ user[0] ] = False
        super().__init__()
    @event_handler(AuthorizedEvent)
    def handle_authorized(self, event):
        # 注册
        global commands
        message = Message(to_jid = self.user[2], body = json.dumps( commands['reg'] ) )
        event.stream.send(message)
        #print('reg user %s'%self.user[0])
        return
    @event_handler(DisconnectedEvent)
    def handle_disconnected(self, event):
        return QUIT
        
class XmppHandler(XMPPFeatureHandler):
    """ user = (jid-0,password-1,srv_jid-2,domain-3,port-4)
        win 是 MainWindow 的引用，用于显示信息 """
    user = None
    win = None
    client = None
    def __init__(self, user,win):
        global status
        self.user = user
        self.win = win
        status[ user[0] ] = False
        super().__init__()
    def set_client(self,client):
        self.client= client
    @message_stanza_handler()
    def handle_message(self, stanza):
        # 消息处理： rd、have、select、wait、ready
        logging.info(u"\nMsg: {0}".format(tostring(stanza.get_xml())))
        from_jid = stanza.from_jid
        try:
            cmd = json.loads( stanza.body )
            if cmd['cmd']=='rd':
                return do_rd(self,cmd)
            elif cmd['cmd'] == 'ok':
                do_ok(self,cmd)
            elif cmd['cmd'] == 'have':
                do_have(self,cmd,stanza.from_jid)
            elif cmd['cmd'] == 'select':
                do_select( self.win,self.client, cmd, stanza.from_jid )
            elif cmd['cmd'] == 'wait':
                do_wait( self.win , cmd)
            elif cmd['cmd'] == 'ready':
                do_ready( self.win, cmd )
        except:
            pass
        return None

class XmppClient(Thread):
    """  XMPP 处理程序  
         XmppClient( user, win )
         user = (jid-0,password-1,srv_jid-2,domain-3,port-4)
         win 是 MainWindow 的引用，用于显示信息
         每个 user 都运行一个线程，在主线程中要保存每个实例的引用 """
    user = None
    win = None
    def __init__(self, user, win):
        super().__init__()
        self.user = user
        self.win = win
    def run(self):
        """ 连接，然后循环接收信息、网络指令 """
        settings = XMPPSettings({ "password": self.user[1],'port':self.user[4],'server':gethostbyname(self.user[3]) })
        jid = JID(self.user[0])
        msg_handler = XmppHandler(self.user,self.win)
        evt_handler = EvtHandler(self.user,self.win)
        self.client = Client(jid, [ msg_handler,evt_handler ], settings)
        self.client.connect()
        msg_handler.set_client( self.client )
        #print('connect user %s'%self.user[0])
        self.client.run()
        return 0
    
def do_rd(handler,cmd):
    #print('recv: rd')
    res = []
    global ident
    for i in range(handler.win.share_list.size()):
        item = handler.win.share_list.get(i)
        try:
            item.lower().index(cmd['key'].lower())
        except:
            continue
        sharename = item
        details = handler.win.share_details[ item ]
        res.append( (sharename,details[1],details[2]) )
    if len(res)==0:
        return None
    else:
        commands['have']['key'] = res
        commands['have']['ident'] = cmd['ident']
        message = Message( to_jid= cmd['from'], body = json.dumps(commands['have']) )
        ident[ cmd['from'] ] = cmd['ident']
        return message
    
def do_have(handler,cmd,from_jid):
    global commands
    if cmd['ident'] == commands['want']['ident']:
        handler.win.show_have(cmd,from_jid.as_string() )
    else:
        print('error ident')
    return
    
def do_ok( handler,cmd=None ):
    #print('recv: ok')
    global status
    if cmd['key']=='reg':
        #print('reg once')
        status[ handler.user[0] ] = True
        handler.win.status_connect(handler.user)
    elif cmd['key']=='quit':
        status[ handler.user[0] ] = False
        handler.win.status_disconnect(handler.user)
    return

def do_select(win,client,cmd,from_jid):
    # 发送 wait 报文，然后上传文件(线程)，记录 
    # send_file[ident] = (sharename,from_jid)
    global send_file
    send_file[ cmd['ident'] ] = ( cmd['key'], from_jid )
    trans = upload_proc(cmd['ident'], client, win)
    trans.start()
    return
    
def do_wait(win , cmd):
    # 显示等待下载
    #print('recv: wait')
    global select_file
    # select_file[ident] = (sharename,sha,size)
    file_desc = select_file[ cmd['ident'] ]
    return

def file_hash(filename):
    """ sha,size = file_hash( pathname ) """
    try:
        size = os.path.getsize(filename)
    except:
        return (None,0)
    if size>11000000:
        return (None,0)
    data=[]
    fp = open(filename,'rb')
    for line1 in fp:
        data.append(line1)
    fp.close()
    dat = b''.join(data)
    sha = sha1( dat )
    return (sha.hexdigest(),size)
    
def download_proc(win,cmd):
    global select_file
    # select_file[ident] = (sharename,sha,size)
    try:
        os.mkdir('tmp')
    except:
        pass
    file_desc = select_file[ cmd['ident'] ]
    filename = 'tmp/'+cmd['ident']+'.gz'
    zsize = cmd['key']
    #print(url)
    sock = socket.socket()
    sock.connect(net_conf.repeator_addr)
    if librepeator.init_connect(sock,cmd['ident']):
        fp = open(filename,'wb')
        sock.settimeout(60)
        while True:
            try:
                bz = sock.recv(1024)
            except Exception as e:
                break
            if len(bz)>0:
                fp.write(bz)
            else:
                break
        fp.close()
        sock.close()
    try:
        os.mkdir('Downloads')
    except:
        pass
    lname = 'Downloads/'+file_desc[0]
    fp = open(lname,'wb')
    p2 = os.popen('gunzip -c %s'%filename)
    while True:
        line1 = os.read(p2.fileno(),1024)
        if len(line1)==0:
            break
        fp.write( line1 )
    fp.close()
    os.unlink(filename)
    win.status_download( file_desc[0] )
    return
    
def do_ready(win, cmd):
    # 下载文件
    #print('recv: ready')
    t = Thread( target = download_proc,args=(win,cmd) )
    t.start()
    
def send_want(client,user,key):
    global username,status
    if status[user[0]]==False:
        return
    ram = '%s+%s+%s'%(username,key,time.time())
    commands['want']['key'] = key
    commands['want']['ident'] = md5(ram.encode('utf-8')).hexdigest()
    message = Message(to_jid = user[2], body = json.dumps( commands['want'] ) )
    client.send(message)
    
#通用的发送请求函数
def send_cmd(client,to_jid , command ):
    message = Message(to_jid = to_jid, body = json.dumps( command ) )
    client.send(message)
    
def send_quit(client,user):
    message = Message(to_jid = user[2], body = json.dumps( commands['quit'] ) )
    client.send(message)
    
def send_reg(client,user):
    message = Message(to_jid = user[2], body = json.dumps( commands['reg'] ) )
    client.send(message)
    
select_file = {}
# 接收方（请求方）：select_file[ident] = (sharename,sha,size)
# {'cmd':'select','key':'','ident':''}
def send_select(client,file_details ):
    # file_details = (sharename-0, sha-1, size-2, from_jid-3)
    commands['select']['key'] = file_details[0]
    commands['select']['ident'] = commands['want']['ident']
    message = Message(to_jid= file_details[3].decode(),body = json.dumps(commands['select']))
    client.send(message)
    select_file[ commands['want']['ident'] ] = file_details

#{'cmd':'wait','ident':''}
def send_wait(client,from_jid):
    global ident
    commands['wait']['ident'] = ident[ from_jid.as_string().decode() ]
    message = Message(to_jid= from_jid,body = json.dumps(commands['wait']))
    client.send(message)
    
class upload_proc(Thread):
    ident = None
    client = None
    win = None
    def __init__(self,ident,client,win):
        self.ident = ident
        self.client = client
        self.win = win
        super().__init__()
    def run(self):
        global send_file, commands
        #发送方：send_file[ident] = (sharename,from_jid)
        pathname = self.win.get_share_path( send_file[self.ident][0] )
        zpath = self.zip_file( pathname )
        send_wait( self.client,send_file[self.ident][1] )
        commands['ready']['ident'] = self.ident
        commands['ready']['key'] = os.path.getsize( zpath )
        send_cmd(self.client,send_file[self.ident][1] , commands['ready'] )
        sock = socket.socket()
        sock.connect( net_conf.repeator_addr )
        if librepeator.init_connect(sock,self.ident):
            fp = open(zpath,'rb')
            while True:
                bz = fp.read(512)
                if len(bz)==0:
                    break
                sock.send(bz)
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()
            fp.close()
        os.unlink(zpath)
        return
        
    def zip_file(self,pathname):
        try:
            os.mkdir('tmp')
        except:
            pass
        zpath = 'tmp/'+self.ident
        fin = open(pathname,'rb')
        fout = open(zpath,'wb')
        sz = os.path.getsize(pathname)
        os.sendfile(fout.fileno(),fin.fileno(),0,sz)
        fin.close()
        fout.close()
        zpipe = os.popen('gzip -9 -f %s'%zpath)
        for line1 in zpipe:
            pass
        return zpath+'.gz'
